﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Threading;
using System.Windows.Forms;
using KinectCore.Gui.Generator;
using xn;
using xnv;

namespace KinectCore.Gui.Controller
{
    //public delegate void ControlHoverEvent(Control c);
    //public delegate void ControlLeaveEvent(Control c);
    //public delegate void ControlActivatedEvent(Control c);

    public class HandsController
    {
        private readonly Color[] colors = { Color.Red, Color.Blue, Color.ForestGreen, Color.Yellow, Color.Orange, Color.Purple, Color.White };
        private readonly Color[] anticolors = { Color.Green, Color.Orange, Color.Red, Color.Purple, Color.Blue, Color.Yellow, Color.Black };
        private readonly int ncolors = 6;


        // OpenNI components
        private Context context;
        private DepthGenerator depthGenerator;
        private UserGenerator userGenerator;
        //private MouseHandsGenerator handsGenerator;
        private HandsGenerator handsGenerator;
        private SessionManager sessionMgr;
        private Dictionary<uint, Point3D> handsList;

        // Drawing components
        private Bitmap bitmap;

        // Threading components
        private Thread readerThread;
        private bool shouldRun;

        // GUI components
        private Control _parentControl;
        private Dictionary<Rectangle, Control> activeControls = new Dictionary<Rectangle, Control>();
        private List<Control> HoveringControls = new List<Control>();

        // Temp vars used in high-efficiency methods
        private List<Control> tempControlList = new List<Control>();
        private float real_to_screen_ratio_x;
        private float real_to_screen_ratio_y;
        private float half_bitmap_x;
        private float half_bitmap_y;
        private Point screenPointOfHand;

        // property accessors
        public Control ParentControl
        {
            get { return _parentControl; }
            set
            {
                // TODO: Re-enable this line for dev with the mouse emulator
                //handsGenerator.ParentControl = null;

                // remove the handler from the previous control
                if (_parentControl != null)
                {
                    lock (_parentControl)
                    {
                        _parentControl.Paint -= new PaintEventHandler(control_Paint);
                    }
                }

                // remember the control
                _parentControl = value;

                lock (_parentControl)
                {
                    // register the control paint event
                    _parentControl.Paint += new PaintEventHandler(control_Paint);


                    real_to_screen_ratio_x = (float)this.bitmap.Width / (float)_parentControl.Width;// / 1280f;
                    real_to_screen_ratio_y = (float)this.bitmap.Height / (float)_parentControl.Height;// / 960f;
                    half_bitmap_x = (float)this.bitmap.Width / 2f;
                    half_bitmap_y = (float)this.bitmap.Height / 2f;

                    // remove any active controls
                    lock (activeControls)
                    {
                        activeControls.Clear();
                    }

                    lock (HoveringControls)
                    {
                        foreach (Control c in HoveringControls)
                        {
                            ControlLeave(c);
                        }
                        HoveringControls.Clear();
                    }

                }

                // TODO: Re-enable this line for dev with the mouse emulator
                //handsGenerator.ParentControl = _parentControl;

            }
        }

        public event ControlHoverEvent ControlHover;
        public event ControlActivatedEvent ControlActivated;
        public event ControlLeaveEvent ControlLeave;

        #region " Constructors / Destructors "
        public HandsController(Context context)
        {
            // get our context and depth map generator
            this.context = context;
            this.activeControls = new Dictionary<Rectangle, Control>();

            // depth generator
            try
            {
                this.depthGenerator = context.FindExistingNode(NodeType.Depth) as DepthGenerator;
            }
            catch (XnStatusException ex)
            {
                // TODO: do we need a better way to notify users?
                Console.Error.WriteLine(ex.Message);
            }

            // get a user generator
            try
            {
                this.userGenerator = new UserGenerator(this.context);
            }
            catch (XnStatusException ex)
            {
                // TODO: do we need a better way to notify users?
                Console.Error.WriteLine(ex.Message);
            }

            if (this.userGenerator != null)
            {
                this.userGenerator.NewUser += new UserGenerator.NewUserHandler(userGenerator_NewUser);
                this.userGenerator.LostUser += new UserGenerator.LostUserHandler(userGenerator_LostUser);
                this.userGenerator.StartGenerating();
            }

            // create session manager
            try
            {
                sessionMgr = new SessionManager(this.context, "RaiseHand");
                sessionMgr.SessionStart += new SessionManager.SessionStartHandler(sm_SessionStart);
            }
            catch (XnStatusException ex)
            {
                // TODO: do we need a better way to notify users?
                Console.Error.WriteLine(ex.Message);
            }

            // hands generator
            this.handsList = new Dictionary<uint, Point3D>();
            //this.handsGenerator = new MouseHandsGenerator(this.context);
            this.handsGenerator = new HandsGenerator(this.context);
            this.handsGenerator.HandCreate += new HandsGenerator.HandCreateHandler(handsGenerator_HandCreate);
            this.handsGenerator.HandDestroy += new HandsGenerator.HandDestroyHandler(handsGenerator_HandDestroy);
            this.handsGenerator.HandUpdate += new HandsGenerator.HandUpdateHandler(handsGenerator_HandUpdate);
            this.handsGenerator.StartGenerating();

            if (this.depthGenerator != null)
            {
                MapOutputMode mapMode = this.depthGenerator.GetMapOutputMode();
                mapMode.nFPS = 30;
                mapMode.nXRes = 640;
                mapMode.nYRes = 480;
                this.depthGenerator.SetMapOutputMode(mapMode);
                this.depthGenerator.FieldOfViewChanged += new StateChangedHandler(depth_FieldOfViewChanged);

                this.bitmap = new Bitmap((int)mapMode.nXRes, (int)mapMode.nYRes/*, System.Drawing.Imaging.PixelFormat.Format24bppRgb*/);

            }

            // init the bitmap
            if (this.depthGenerator == null)
                this.bitmap = new Bitmap(1000, 1000);

            // init the thread
            this.shouldRun = true;
            this.readerThread = new Thread(ReaderThread);


        }

        ~HandsController()
        {
            Stop();
        }
        #endregion
        #region " Kinect events "
        void depth_FieldOfViewChanged(ProductionNode node)
        {
            handsList.Clear();
            if (sessionMgr != null)
                sessionMgr.EndSession();

            // unhover ALL controls
            lock (HoveringControls)
            {
                while (HoveringControls.Count > 0)
                {
                    Control c = HoveringControls[0];

                    if (!tempControlList.Contains(c))
                    {
                        // control is not being hovered
                        ControlLeave(c);
                        HoveringControls.Remove(c);
                    }
                }
                lock (tempControlList)
                {
                    tempControlList.Clear();
                }
            }


        }

        void sm_SessionStart(ref Point3D position)
        {
            // recapture the hand tracking working-vars (in case things have changed - it happens!!)
            CalcScalingRatios();

            this.handsGenerator.StartTracking(ref position);
        }

        void handsGenerator_HandCreate(ProductionNode node, uint id, ref Point3D position, float fTime)
        {
            Console.WriteLine("Create hand " + id.ToString() + " at " + position.ToString());

            CalcScalingRatios();

            lock (handsList)
            {
                handsList.Add(id, position);
            }
        }

        void handsGenerator_HandDestroy(ProductionNode node, uint id, float fTime)
        {
            Console.WriteLine("Lost tracking for hand " + id.ToString());
            lock (handsList)
            {
                handsList.Remove(id);
            }

            if (sessionMgr != null)
                sessionMgr.EndSession();
        }

        void handsGenerator_HandUpdate(ProductionNode node, uint id, ref Point3D position, float fTime)
        {
            //Console.WriteLine("Updating hand " + id.ToString() + " to " + position.ToString());
            lock (handsList)
            {
                handsList.Remove(id);
                handsList.Add(id, position);
            }

            // check if we're over a button
            try
            {
                // PSK: removed on order of Stevie - new code seems to work better
                //RealPointToScreen(position, ref screenPointOfHand);
                screenPointOfHand.X = (int)(position.X + half_bitmap_x);
                screenPointOfHand.Y = (int)(-position.Y + half_bitmap_y);

                tempControlList.Clear();

                foreach (Rectangle r in activeControls.Keys)
                {
                    if (r.Contains(screenPointOfHand))
                    {
                        Control c = activeControls[r];

                        // remember this control is being hovered
                        tempControlList.Add(c);

                        // see if the control is a new hover
                        if (HoveringControls.Contains(c))
                        {
                            // do nothing
                        }
                        else
                        {
                            // mark as hovering
                            ControlHover(activeControls[r]);
                            HoveringControls.Add(activeControls[r]);
                        }
                    }

                    // see which controls are no longer being hovered
                    for (int i = 0; i < HoveringControls.Count; i++)
                    {
                        Control c = HoveringControls[i];

                        if (!tempControlList.Contains(c))
                        {
                            // control is not being hovered
                            ControlLeave(c);
                            HoveringControls.Remove(c);
                            i--;
                        }
                    }

                    // TODO: activate controls
                }


            }
            catch (Exception e)
            {
                Console.WriteLine(e.Message);
            }


        }

        void userGenerator_NewUser(ProductionNode node, uint id)
        {
            //this.poseDetectionCapability.StartPoseDetection(this.calibPose, id);
        }

        void userGenerator_LostUser(ProductionNode node, uint id)
        {
            //this.joints.Remove(id);
        }
        #endregion
        #region " Control painting methods "

        // Event called when control is repainted
        private void control_Paint(object sender, PaintEventArgs e)
        {
            // draw our buffer bitmap onto the control
            e.Graphics.DrawImage(this.bitmap,
                0, //this.control.Location.X,
                0, //this.control.Location.Y,
                this._parentControl.Size.Width,
                this._parentControl.Size.Height);


            // invalidate all children
            InvalidateAllChilren(this._parentControl);
        }
        #endregion
        #region " Threading methods "
        private unsafe void ReaderThread()
        {
            DepthMetaData depthMD = new DepthMetaData();

            while (this.shouldRun)
            {
                if (this.depthGenerator != null)
                    try
                    {
                        this.context.WaitOneUpdateAll(this.depthGenerator);
                    }
                    catch (Exception)
                    {
                    }

                // skip if no parent control
                if (this._parentControl == null)
                    continue;

                if (this.depthGenerator != null)
                    this.depthGenerator.GetMetaData(depthMD);

                //CalcHist(depthMD);

                lock (this._parentControl)
                {

                    Rectangle rect = new Rectangle(0, 0, this.bitmap.Width, this.bitmap.Height);

                    if (this.depthGenerator != null && this.userGenerator != null)
                    {
                        BitmapData data = this.bitmap.LockBits(rect, ImageLockMode.WriteOnly, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
                        // draw pixels
                        ushort* pDepth = (ushort*)this.depthGenerator.GetDepthMapPtr().ToPointer();
                        ushort* pLabels = (ushort*)this.userGenerator.GetUserPixels(0).SceneMapPtr.ToPointer();

                        // set pixels
                        for (int y = 0; y < depthMD.YRes; ++y)
                        {
                            byte* pDest = (byte*)data.Scan0.ToPointer() + y * data.Stride;
                            for (int x = 0; x < depthMD.XRes; ++x, ++pDepth, ++pLabels, pDest += 3)
                            {
                                pDest[0] = pDest[1] = pDest[2] = 0;

                                ushort label = *pLabels;
                                if (*pLabels != 0)
                                {
                                    Color labelColor = Color.White;
                                    if (label != 0)
                                    {
                                        labelColor = colors[label % ncolors];
                                    }

                                    pDest[0] = labelColor.B;
                                    pDest[1] = labelColor.G;
                                    pDest[2] = labelColor.R;
                                }
                            }
                        }
                        this.bitmap.UnlockBits(data);
                    }

                    Graphics g = Graphics.FromImage(this.bitmap);

                    // if we're not drawing the depth info we need to blank the image
                    if (this.depthGenerator == null || this.userGenerator == null)
                    {
                        g.FillRectangle(Brushes.Gray, rect);
                    }

                    // draw hands
                    lock (handsList)
                    {

                        foreach (Point3D hand in handsList.Values)
                        {
                            RealPointToScreen(hand, ref screenPointOfHand);

                            // draw horizontal line
                            g.DrawLine(Pens.Red, 0, screenPointOfHand.Y, this.bitmap.Width, screenPointOfHand.Y);
                            // draw vertical line
                            g.DrawLine(Pens.Red, screenPointOfHand.X, 0, screenPointOfHand.X, this.bitmap.Height);

                            g.FillEllipse(Brushes.Pink, screenPointOfHand.X - 4, screenPointOfHand.Y - 4, 9, 9);

                        }
                    }

                    //uint[] users = this.userGenerator.GetUsers();
                    //foreach (uint user in users)
                    //{


                    //}
                    g.Dispose();
                }



                // cause the control to be redrawn
                // also invadate the children
                InvalidateControlAndChildren(_parentControl);

            }
        }

        private static void InvalidateControlAndChildren(Control c)
        {

            c.Invalidate();

            foreach (Control child in c.Controls)
                child.Invalidate();
        }

        public void Start()
        {
            this.readerThread.Start();
        }

        public void Stop()
        {
            this.shouldRun = false;
            try
            {
                if (this.readerThread != null && this.readerThread.ThreadState != ThreadState.Unstarted)
                    this.readerThread.Join();
            }
            catch (Exception ex)
            {
                Console.Error.WriteLine(ex.Message);
            }
        }
        #endregion

        private void InvalidateAllChilren(Control c)
        {
            foreach (Control child in c.Controls)
            {
                child.Refresh();
                InvalidateAllChilren(child);
            }
        }

        private void CalcScalingRatios()
        {
            lock (_parentControl)
            {
                real_to_screen_ratio_x = (float)this.bitmap.Width / (float)_parentControl.Width;// / 1280f;
                real_to_screen_ratio_y = (float)this.bitmap.Height / (float)_parentControl.Height;// / 960f;
                half_bitmap_x = (float)this._parentControl.Width / 2f;
                half_bitmap_y = (float)this._parentControl.Height / 2f;
            }
        }

        private void RealPointToScreen(Point3D hand, ref Point output)
        {
            float screen_x = (hand.X + half_bitmap_x) * real_to_screen_ratio_x;
            float screen_y = (-hand.Y + half_bitmap_y) * real_to_screen_ratio_y;
            output.X = (int)screen_x;
            output.Y = (int)screen_y;
        }

        public void AddControl(Control c)
        {
            Rectangle r = new Rectangle(c.Location, c.Size);

            lock (activeControls)
            {
                activeControls.Add(r, c);
            }
        }

        public void ClearControls()
        {
            lock (activeControls)
            {
                activeControls.Clear();
            }
        }

    }
}
